import hashlib
import select
import string
import struct
from multiprocessing import Pool
import random
from socket import *
import os
from pymysql import *


class Server:
    # 地址是自己的IP，端口最好大于1024，服务器不绑定地址也可以，但客户端必须绑要不然不知道发给谁
    def __init__(self,ip,port):
        # 注解要写到init位置
        self.s_listen:socket=None  # 用来listen的socket对象,有128个服务员的那个
        self.ip=ip
        self.port=port
        self.tcp_init()
        self.po=Pool(3)
        # 获取当前目录
        self.sever_path = os.getcwd()
        # 用于统一存放文件的路径，系统设置用户名长度大于4，不会发生与用户路径冲突的问题
        self.sever_file_path=self.sever_path+'/ser'
        # 修改工作目录为sever_path
        os.chdir(self.sever_path)
        # 在此目录下新建目录ser,就有了这个统一存放文件的路径sever_file_path
        try:
            os.mkdir('ser')
        except:
            pass
        # 创建一个epoll对象
        self.epoll=select.epoll()
        self.epoll.register(self.s_listen.fileno(),select.EPOLLIN)
        # 存放已经连接的用户
        self.user_dist={}
        # 存放token
        self.token={}

    def tcp_init(self):
        #先启动server,tcp和udp都是先运行服务器，提前等着客户端
        self.s_listen = socket(AF_INET, SOCK_STREAM)
        # SO_REUSERADDR 允许重用本地地址和端口 int
        self.s_listen.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
        self.s_listen.bind((self.ip,self.port))  # 绑定后并没有激活端口
        self.s_listen.listen(128)  # 这时端口才会激活,是缓冲区最多可以放128个客户端的连接

    def task(self):
        while True:
            # epoll.poll（）是当文件句柄发生变化，会以列表的形式主动报告给用户进程
            events=self.epoll.poll()
            for fd,event in events:
                # 如果活动socket为当前服务器socket，表示有新连接
                if fd ==self.s_listen.fileno():
                    new_client, client_addr = self.s_listen.accept()
                    #连接成功后，接收客户端传来的指令，比如注册或登录这些，所以要接收
                    data=recv_train(new_client).decode('utf8')
                    # 如果客户端发送的是000，注册用户
                    if data=='000':
                        print('注册信息')
                        user_name=recv_train(new_client).decode('utf8')
                        password=recv_train(new_client).decode('utf8')
                        #如果用户名之前存在
                        if conn.select1('user','name',user_name):
                            send_train('用户名已存在',new_client)
                        else:
                            send_train('注册成功',new_client)
                            conn.insert('user',(0,user_name,password))
                            # 为每个新用户创建一个目录（这个目录会创建到self.sever_path下)
                            os.chdir(self.sever_path)
                            os.mkdir(user_name)
                    # 如果客户端发的是用户名，监控new_client，发送token
                    elif conn.select1('user','name',data):
                        print('收到用户名')
                        password=recv_train(new_client).decode('utf8')
                        print('收到密码')
                        # 判断用户密码是否正确
                        if conn.select2('user','name','password',data)==password:
                            send_train('登陆成功',new_client)
                            user_name=data
                            self.epoll.register(new_client.fileno(),select.EPOLLIN)
                            print('将new_client放进进程池')
                            user=User()
                            # 用户自己的目录初始为/
                            user.path='/'
                            user.name=user_name
                            user.hide_len=len(self.sever_path+'/'+user.name)
                            user.new_client=new_client
                            # 用字典来存储用户
                            self.user_dist[new_client.fileno()]=user
                            token=get_token()
                            user.send_train(token)
                            # 建立token和user之间的映射
                            self.token[token]=user
                            user.token=token
                        else:
                            send_train('密码错误',new_client)
                    # 如果客户端发的是token，new_client通过apply_aysnc派上传或者下载的任务给子进程
                    elif data in self.token:
                        # 根据token找到用户
                        user=self.token[data]
                        command=recv_train(new_client).decode('utf8')
                        # 服务器端的工作路径，用户不感知
                        print(self.sever_path)
                        print(user.path)
                        print(user.name)
                        work_path = self.sever_path + '/' + user.name + '/' + user.path
                        print(work_path)
                        if command[:4]=='gets':
                            self.po.apply_async(do_gets,args=(command,new_client,work_path))
                        elif command[:4]=='puts':
                            self.po.apply_async(do_puts,args=(command,new_client,work_path,self.sever_file_path,user))
                    # 收到的是用户名，但是数据库中没有
                    else:
                        send_train('不存在此用户',new_client)

                # 如果是可读事件（非新连接的响应，即客户端响应）ls等命令的处理在这
                elif event==select.EPOLLIN:
                    # 查出是哪个用户
                    user=self.user_dist[fd]
                    # 接收用户的指令
                    try:
                        # 接命令，用接火车的方式，接到命令（字节流），需要解码
                        command=user.recv_train().decode('utf8')
                    except struct.error:
                        #客户端断开
                        self.epoll.unregister(fd)
                        user.new_client.close()
                        del self.user_dist[fd]
                        del self.token[user.token]
                    else:
                        print(self.sever_path)
                        print(user.path)
                        print(user.name)
                        work_path=self.sever_path+'/'+user.name+'/'+user.path
                        print(work_path)
                        user.deal_command(command,work_path,self.serve_file_path)

        # 接受客户端的连接，128里出来一个 它和客户端通信 accept以后服务器就知道发给谁了
        # user=User(new_client)
        # return user


class User:
    #每个用户user有一个对应的客户端连接
    def __init__(self):
        self.new_client:socket=None
        self.name=None
        self.token=None
        self.path=os.getcwd()#存储连上的用户的路径 eg.E:\python_study\my_python\my_netdisc
        self.hide_len = None

    def deal_command(self,command,*args):
        # 服务器接各种命令
        while True:
            # 切片，得字符串的前两个
            if command[:2] == 'ls':
                self.do_ls()
            elif command[:2] == 'cd':
                self.do_cd(command)
            elif command[:3] == 'pwd':
                self.do_pwd()
            elif command[:2] == 'rm':
                self.do_rm(command,args[0],args[1])
            # elif command[:4] == 'gets':
            #     self.do_gets(command)
            # elif command[:4] == 'puts':
            #     self.do_puts(command)
            else:
                print('wrong command')

    # 基础文件操作六命令
    def do_ls(self):
        # listdir获取目录列表（只考虑文件名，文件大小）
        # os.stat(file)是显示文件信息，再.st_size就是文件大小（int类型），要把int转成str拼接在一起
        data=''
        for file in os.listdir(self.path):
            #显示即为（文件名 文件大小），\n是换行
            data+=file+''*5+str(os.stat(file).st_size)+'\n'
        print(data)
        self.send_train(data.encode('utf8'))

    def do_cd(self,command):
        path=command.split()[1]
        #chdir为修改工作目录
        os.chdir(path)
        self.path=os.getcwd()
        # 客户端每次执行了cd后，服务器给他返回一下新的当前路径
        self.send_train(self.path.encode('utf8'))

    def do_pwd(self):
        self.send_train(self.path.encode('utf8'))

    def do_rm(self, command, work_path, server_file_path):
        os.chdir(work_path)
        file_name = command.split()[1]
        try:
            md5_num = md5(file_name)
            os.remove(file_name)
        except Exception as e:
            self.send_train(str(e))
        else:
            # 得到该文件的引用值，如果为1，说明是最后一个，将记录删除
            # 如果不为1，则-1后更新
            count = conn.select2('sever_file', 'md5', 'count', md5_num)
            if count == 1:
                conn.delete('sever_file', 'md5', md5_num)
                os.remove(server_file_path + '/' + file_name)
            else:
                new_count = count - 1
                conn.update('sever_file', 'count', new_count, count)
            self.send_train('删除成功')

    # def do_gets(self,command):
    #     # 文件下载
    #     print(self.user_name)
    #     file_detail=Server.show_file_detail(Server.main_path+'/'+self.user_name)
    #     self.send_train(file_detail)
    #     file_name=self.revc_train().decode('utf8')
    #     self.send_train(file_name)
    #
    # def do_puts(self,command):
    #     # 文件上传
    #     pass

    def send_train(self, send_bytes):
        # send火车，就是把某个字节流内容以火车形式发过去
        # 每次发东西，先拿到头部，再拿到内容（不管文件名还是文件内容）
        train_head_bytes = struct.pack('I', len(send_bytes))
        self.new_client.send(train_head_bytes + send_bytes)

    def recv_train(self):
        # recv火车，就是把火车recv的内容返回出去
        # 先接到火车头，再根据火车头的长度收到火车内容
        train_head_bytes = self.new_client.recv(4)
        train_head = struct.unpack('I', train_head_bytes)
        return self.new_client.recv(train_head[0])


class Conn_Mysql:
    def __init__(self, host, port, user, password, database):
        self.host = host
        self.port = port
        self.user = user
        self.password = password
        self.database = database
        self.config = {
            "host": self.host,
            "port": self.port,
            "user": self.user,
            "password": self.password,
            "database": self.database,
            "charset": "utf8"
        }
        self.conn = connect(**self.config)
        self.cur = self.conn.cursor()

    def close(self):
        self.conn.close()
        self.cur.close()

    def insert(self, table_name, *args):
        """
        往指定的表中插入数据
        :param table_name:表名称
        :param args:要插入的数据
        :return:None
        """

        for item in args:
            sql = "INSERT INTO {} VALUES{};".format(table_name, item)
            self.cur.execute(sql)
            self.conn.commit()

    def select1(self, table_name, *args):
        # 查询指定表中的数据，有返回1，没有返回0
        sql = "SELECT {} FROM {}; ".format(args[0], table_name)
        count = self.cur.execute(sql)
        for i in range(count):
            result = self.cur.fetchone()
            if args[1] in result:
                return 1
        return 0

    def select2(self,table_name,*args):
        # 查询标中args[0]列等于args[2]的行中 arg[1]列的元素
        sql = "SELECT {}, {} FROM {} WHERE {}='{}'; ".format(args[0],args[1],table_name,args[0],args[2])
        self.cur.execute(sql)
        result=self.cur.fetchone()
        return result[1]

    def update(self, table_name, *args):
        # 更新表中的数据
        sql = "UPDATE {} SET {}='{}' WHERE {}='{}';".format(table_name, args[0], args[1], args[0], args[2])
        self.cur.execute(sql)
        self.conn.commit()

    def delete(self, table_name, identify, *args):
        # 删除指定表的指定数据
        for item in args:
            print(identify)
            print(item)
            sql = "DELETE FROM {} WHERE {}='{}';".format(table_name, identify, item)
            self.cur.execute(sql)
            self.conn.commit()


def do_gets(command,new_client,work_path):
    pass

def do_puts(command:str,new_client,work_path,sever_file_path,user):
    pass

# 进程池po.apply_async只支持纯方法传入，不支持对象方法
# def run_proc(user):
#     user.deal_command()

# md5算法
def md5(file_name):
    f = open(file_name, 'rb')
    f_md5 = hashlib.md5()
    f_md5.update(f.read())
    f.close()
    return (f_md5.hexdigest())

# 获得token
def base_str():
    return (string.ascii_letters + string.digits)

def get_token():
    keylist = [random.choice(base_str()) for i in range(30)]
    return (''.join(keylist))


def send_train(send_bytes,client):
    # send火车，就是把某个字节流内容以火车形式发过去
    # 每次发东西，先拿到头部，再拿到内容（不管文件名还是文件内容）
    train_head_bytes=struct.pack('I',len(send_bytes))
    client.send(train_head_bytes+send_bytes)

def recv_train(client):
    # recv火车，就是把火车recv的内容返回出去
    # 先接到火车头，再根据火车头的长度收到火车内容
    train_head_bytes=client.recv(4)
    train_head=struct.unpack('I',train_head_bytes)
    return client.recv(train_head[0])


if __name__ == '__main__':
    # pycharm远程连接mysql数据库
    conn=Conn_Mysql('192.168.124.132',3306,'root','123','wangpan')
    server=Server('',2000)# 地址是自己的IP，端口最好大于1024，服务器不绑定地址也可以，但客户端必须绑要不然不知道发给谁
    server.task()
    # server.tcp_init()
    # 下面的代码是没加epoll，刚加了进程池的时候写的
    # po=Pool(3)
    # while True:
    #     user=server.task()
    #     po.apply_async(run_proc, args=(user,))
        # user.deal_command()  # 调用user的方法来处理客户端发来的各种命令

